package com.hvilela.nuvem {

	/**
	 * @author hvilela
	 */
	public class FuzzyEngine {

		private static const VARIABLE : int = 0;
		private static const NUMBER : int = 1;
		private static const OPERATOR : int = 2;
		private static const UNARYOPERATOR : int = 3;
		private static const BRACKET : int = 4;

		public static function inverseMembershipFunction(func:Function):Function {
			return function(value:Number):Number {
				return 1 - func(value);
			};
		}

		public static function createLinearMembershipFunction(a:Number, b:Number):Function {
			var min:Number = Math.min(a, b);
			var max:Number = Math.max(a, b);

			var func:Function = function(value:Number):Number {
				if (value < min) {
					return 0;
				}
				
				if (value > max) {
					return 1;
				}

				return (value - min) / (max - min);
			};

			if (a > b) {
				func = inverseMembershipFunction(func);
			}

			return func;
		}
		
		public static function createTriangularMembershipFunction(min:Number, center:Number, max:Number):Function {
			return function(value:Number):Number {
				if (value < min) {
					return 0;
				}

				if (value > max) {
					return 0;
				}

				if (value < center) {
					return (value - min) / (center - min);
				} else {
					return 1 - (value - center) / (max - center);
				}
			};
		}

		public static function eval(expression : String, values : Object, functions:Object) : Number {
			trace(expression);

			expression = expression.replace(/ OR /g, " | ").replace(/ AND /g, " & ").replace(/ IS /g, " ~ ").replace(/ NOT /g, "!").replace(/ /g, "");
			for (var varName:String in values) {
				expression = expression.replace(new RegExp(varName, "g"), values[varName]);
			}

			trace(expression);

			var chars:Array = expression.split("");

			var tokens : Array = new Array();
			while (chars.length) {
				tokens.push(getToken(chars));
			}

			var value : Number = parseExpression(tokens, functions);

			if (tokens.length) {
				throw new Error("Unexpected tokens " + tokens);
			}
			
			return value; 
		}

		private static function getToken(chars : Array) : Array {			
			var token : String = chars.shift();
			var type : int = getCharType(token);
			
			if (type == NUMBER || type == VARIABLE) { 
				while (chars.length > 0 && getCharType(chars[0]) == type) {
					token += chars.shift();
				}
			}

			return [token, type];
		}

		private static function getCharType(char : String) : int {
			if (char == "(" || char == ")") {
				return BRACKET;
			} else if (char == "&" || char == "|" || char == "~") {
				return OPERATOR;
			} else if (char == "!") {
				return UNARYOPERATOR;
			} else if (char == "." || parseInt(char).toString() == char) {
				return NUMBER;
			} else {
				return VARIABLE;
			}
		}

		private static function parseExpression(tokens : Array, functions:*) : Number {
			var token : Array = tokens.shift();
			var value : Number;

			if (token[1] == NUMBER) {
				return parseFloat(token[0]);
			}

			if (token[1] == UNARYOPERATOR) {
				value = parseExpression(tokens, functions);

				trace("NOT " + value + " = " + (1 - value));
				
				return 1 - value;
			}

			var left:Number;
			var operator:String;
			var right:*; 
 
			if (token[0] != "(") {
				throw new Error("Open bracket expected " + token);
			}
			
			left = parseExpression(tokens, functions);
			operator = parseOperator(tokens.shift());

			if (operator == "~") {
				right = parseFunction(tokens.shift(), functions);
			} else {
				right = parseExpression(tokens, functions);
			} 

			token = tokens.shift();
			if (token[0] != ")") {
				throw new Error("Close bracket expected " + token);
			}

			if (operator == "&") {
				value = Math.min(left, right);
			} else if (operator == "|") {
				value = Math.max(left, right);
			} else {
				value = functions[right](left);
			}

			trace(left + " " + operator + " " + right + " = " + value);

			return value;
		}

		private static function parseFunction(token : Array, functions : *) : String {
			if (token[1] != VARIABLE && functions[token[0]] != null) {
				throw new Error("Function expected " + token);
			}

			return token[0];
		}

		private static function parseOperator(token : Array) : String {
			if (token[1] != OPERATOR) {
				throw new Error("Operator expected " + token);
			}

			return token[0];
		}

		public static function evalToBool(expression : String, values : Object, functions:Object):Boolean {
			return toBool(eval(expression, values, functions));
		}

		public static function toBool(fuzzy:Number):Boolean {
			return Math.random() <= fuzzy;
		}
	}
}